namespace EyeOpen.Data.DataAnnotations.Nhibernate
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel.DataAnnotations;
	using System.Data;
	using System.Reflection;

	using EyeOpen.Linq;

	using NHibernate.Mapping.ByCode;

	internal class PrimitivePropertyMapper<T>
		where T : class
	{
		private readonly IMinimalPlainPropertyContainerMapper<T> annotationsMap;

		private readonly string keyProperty;

		private readonly IDictionary<PropertyInfo, object[]> propertiesAttributes;

		public PrimitivePropertyMapper(IMinimalPlainPropertyContainerMapper<T> annotationsMap, string keyProperty, IDictionary<PropertyInfo, object[]> propertiesAttributes)
		{
			this.annotationsMap = annotationsMap;
			this.keyProperty = keyProperty;
			this.propertiesAttributes = propertiesAttributes;
		}

		public void Map()
		{
			propertiesAttributes
				.ForEach(MapPrimitiveProperty);
		}

		private void MapPrimitiveProperty(KeyValuePair<PropertyInfo, object[]> propertyAttributes)
		{
			var propertyInfo = propertyAttributes.Key;

			if (propertyInfo.Name == keyProperty)
			{
				return;
			}

			var attributes = propertyAttributes.Value;

			var required = attributes.Any<RequiredAttribute>();

			var length = attributes.SingleOrDefault<StringLengthAttribute>();

			CheckLengthAttributeIsValid(propertyInfo.PropertyType, length);

			MapProperty(propertyInfo.Name, required, length);
		}

		private void MapProperty(string name, bool required, StringLengthAttribute stringLengthAttribute)
		{
			if (stringLengthAttribute == null)
			{
				annotationsMap.Property(name, m => m.NotNullable(required));
				return;
			}

			annotationsMap
				.Property(
					name,
					m =>
						{
							m.NotNullable(required);
							m.Length(stringLengthAttribute.MaximumLength);
						});
		}

		/// <exception cref="MappingException">The StringLength attribute can be applied only to string properties.</exception>
		private void CheckLengthAttributeIsValid(Type propertyType, StringLengthAttribute length)
		{
			if (length == null)
			{
				return;
			}

			if (propertyType != typeof(string))
			{
				throw new MappingException("The StringLength attribute can be applied only to string properties.");
			}
		}
	}
}